﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
using DCT.AutoUpdate.Code.EventTrigger;

namespace DCT.AutoUpdate.Code.Downloader
{
    /// <summary>
    /// 单文件下载
    /// </summary>
    public class SingleFileDownloader
    {
        #region 属性
        /// <summary>
        /// 下载到硬盘的目标
        /// </summary>
        public LocalTargetLocation Target { get; set; }
        /// <summary>
        /// 下载文件目标
        /// </summary>
        public SourceLocation Source { get; set; }
        /// <summary>
        /// 当前下载状态
        /// </summary>
        public FileDownloadStatus Status { get; private set; }
        
        bool downsucc = false;
        #endregion

        #region EventTrigger
        private EventTrigger<SingleFileDownloader> statusChanged =
            EventTrigger<SingleFileDownloader>.CreateInstance();

        public EventTriggerOutter<SingleFileDownloader> EventStatusChanged
        {
            get
            {
                return this.statusChanged.TriggerOutter;
            }
        }

        private EventTrigger<ReceiveInfo> receivingData =
            EventTrigger<ReceiveInfo>.CreateInstance();

        public EventTriggerOutter<ReceiveInfo> EventReceivingData
        {
            get
            {
                return this.receivingData.TriggerOutter;
            }
        }

        #region ReceiveInfo
        public class ReceiveInfo
        {
            public int Size { get; private set; }
            public int NumBytesRead { get; private set; }
            public int NumBytesToRead { get; private set; }
            public int ActuallyRead { get; private set; }
            public int ReadBuffer { get; private set; }
            public double Speed { get; private set; }

            public ReceiveInfo(int size, int numByteRead, int numByteToRead, int actuallyRead, int readBuffer, double speed)
            {
                this.Size = size;
                this.NumBytesRead = numByteRead;
                this.NumBytesToRead = numByteToRead;
                this.ActuallyRead = actuallyRead;
                this.ReadBuffer = readBuffer;
                this.Speed = speed;
            }
        }
        #endregion 
        #endregion

        #region ctor
        /// <summary>
        /// 构造函数
        /// </summary>
        public SingleFileDownloader()
            : this(null, null)
        {
        }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="source">下载源</param>
        /// <param name="target">下载硬盘目标</param>
        public SingleFileDownloader(SourceLocation source, LocalTargetLocation target)
        {
            this.Source = source;
            this.Target = target;
            Status = FileDownloadStatus.Idle;
        } 
        #endregion

        #region 方法
        /// <summary>
        /// 启动下载
        /// </summary>
        public void Start()
        {
            //初始化
            this.SetStatus(FileDownloadStatus.Preparing);
            Stream responseStream = null;
            FileStream fs = null;

            //临时下载名称文件
            string tmp = this.Target.PhysicalFilePath + DateTime.Now.Ticks + ".tmp";
            try
            {
                //下载请求
                using (SourceLocationProtocalProvider pp    //根据下载地址，选择解析协议的方法
                    = SourceLocationProtocalProvider.Create(this.Source.Address))
                {
                    //首先获取文件信息和Response的流
                    pp.GetStream(this.Source, out responseStream, out sourceInfo);

                    //在硬盘占领空间（先下载到临时文件）
                    fs = File.Create(tmp);  //创建临时文件
                    fs.SetLength(SourceInfo.Size);

                    int allSize = (int)SourceInfo.Size;
                    int buffer = 1024 * 10*2;                     //设置下载buffer
                    int numBytesToRead = allSize;               //总共要下载的数量
                    int numBytesRead = 0;                       //已经下载的数量
                    double speed = 0;                           //速度
                    Stopwatch watch = null;                     //计算速度的计时器

                    //准备完毕，要下载了
                    this.SetStatus(FileDownloadStatus.Downloading);

                    watch = Stopwatch.StartNew();
                    while (numBytesToRead > 0)
                    {
                        //下载的buffer与需要下载的字节相比，哪个小取哪个作为真正的buffer
                        int readbuffer = (int)Math.Min(numBytesToRead, buffer);
                        byte[] bytes = new byte[readbuffer];

                        // Read may return anything from 0 to buffer.
                        int actuallyRead = responseStream.Read(bytes, 0, readbuffer);
                        // The end of the file is reached.
                        if (actuallyRead == 0)
                        {
                            break;
                        }
                        else
                        {
                            lock (fs)
                            {
                                //一边下载，一边写入
                                fs.Write(bytes, 0, actuallyRead);
                            }
                        }
                        //更新计算已下载和将要下载
                        numBytesRead += actuallyRead;
                        numBytesToRead -= actuallyRead;
                        //计算速度
                        speed = ((double)numBytesRead / 1024d) / watch.Elapsed.TotalSeconds;
                        this.receivingData.Fire(new ReceiveInfo(allSize, numBytesRead, numBytesToRead, actuallyRead, readbuffer, speed));
                    }
                    watch.Stop();
                    //下载成功
                    this.SetStatus(FileDownloadStatus.Success);
                    downsucc = true;
                    //计算最终下载时间 k/s
                    this.CompletedDownloadSpeed = ((double)numBytesRead / 1024d) / watch.Elapsed.TotalSeconds;
                }

            }
            catch (Exception ex)
            {
                this.InnerException = ex;
                downsucc = false;
                this.SetStatus(FileDownloadStatus.Error);
            }
            finally
            {
                if (fs != null) //释放文件流
                {
                    fs.Dispose();
                }
                if (downsucc)   //如果下载成功，就复制到真正的文件
                {
                    File.Copy(tmp, this.Target.PhysicalFilePath, true);
                }
                //删除临时文件
                File.Delete(tmp);

                this.SetStatus(FileDownloadStatus.Completed);
            }
        }

        public Exception InnerException { get; private set; }

        public bool DownSucc { get { return this.downsucc; } }

        /// <summary>
        /// 单位k/s
        /// </summary>
        public double CompletedDownloadSpeed
        {
            get;
            private set;
        }

        private SourceInfo sourceInfo = null;

        public SourceInfo SourceInfo
        {
            get { return sourceInfo; }
        }

        private void SetStatus(FileDownloadStatus status)
        {
            this.Status = status;
            statusChanged.Fire(this);
        } 
        #endregion
    }

    public class TestTest
    {
        public static void Down()
        {
            LocalTargetLocation ltl = new LocalTargetLocation(@"F:\flashmail-2.1.exe");
            SourceLocation sl = new SourceLocation("http://fm.dl.126.net/NxMail/Update/installer/flashmail-2.1.exe");
            SingleFileDownloader downloader = new SingleFileDownloader(sl, ltl);
            downloader.EventStatusChanged.Add(delegate(SingleFileDownloader _downloader) {
                Console.WriteLine("status changed:=" + _downloader.Status.ToString());
                return true;
            });
            downloader.EventReceivingData.Add(_receiveInfo => {
                Console.WriteLine("receiving...." + "speed=" + _receiveInfo.Speed.ToString() + "k/s" 
                    + "|"
                    + _receiveInfo.NumBytesRead.ToString() + "/" + _receiveInfo.Size.ToString());
                return true;
            });
            downloader.Start();
        }
    }
}
